﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;

namespace EffiProz.Core
{
    public class EfzDateTime
    {
         /**
     * A reusable static value for today's date. Should only be accessed
     * by getToday()
     */
        
    private static long          currentDateMillis;
    private static Calendar tempCalDefault = new GregorianCalendar();
    public static  Calendar tempCalGMT =
        new GregorianCalendar(GregorianCalendarTypes.USEnglish);
    private static  DateTime   tempDate        = new DateTime(0);
    private const String sdfdPattern     = "yyyy-MM-dd";
  
    private const String sdftPattern     = "HH:mm:ss";
 
    private const String sdftsPattern    = "yyyy-MM-dd HH:mm:ss";

    private const String sdftsSysPattern = "yyyy-MM-dd HH:mm:ss.fff";


    static EfzDateTime(){       
         
        currentDateMillis = getNormalisedDate(DateTime.Now.Ticks/TimeSpan.TicksPerMillisecond);
    }

    public static long getDateSeconds(String s) {
       
        try {
            lock (sdfdPattern)
            {
                DateTime d = DateTime.ParseExact(s, sdfdPattern, null);

                return d.Ticks / TimeSpan.TicksPerSecond;
            }
            
        } catch (Exception ) {
            throw Error.error(ErrorCode.X_22007);
        }
    }

    public static String getDateString(long seconds) {

        lock (sdfdPattern) {
            sysDate = new DateTime(seconds * TimeSpan.TicksPerSecond);

            return sysDate.ToString(sdfdPattern);
        }
    }

    public static long getTimestampSeconds(String s) {

        try {
            lock (sdftsPattern) {
                DateTime d = DateTime.ParseExact(s,sdftsPattern, null);

                return d.Ticks / TimeSpan.TicksPerSecond;
            }
        } catch (Exception ) {
            throw Error.error(ErrorCode.X_22007);
        }
    }

    public static void getTimestampString(StringBuilder sb, long seconds,
                                          int nanos, int scale) {

        lock (sdftsPattern) {
            tempDate = new DateTime(seconds * TimeSpan.TicksPerSecond);
            sb.Append(tempDate.ToString(sdftsPattern));

            if (scale > 0) {
                sb.Append('.');
                sb.Append(StringUtil.toZeroPaddedString(nanos, 9, scale));
            }
        }
    }

    public static String getTimestampString(long millis) {

        lock (sdftsPattern) {
           sysDate = new DateTime(millis * TimeSpan.TicksPerMillisecond);

           return sysDate.ToString(sdftsPattern);
        }
    }

    public static long getCurrentDateMillis(long millis) {
        lock (typeof(EfzDateTime))
        {
            if (millis - currentDateMillis >= 24 * 3600 * 1000)
            {
                currentDateMillis = getNormalisedDate(millis);
            }

            return currentDateMillis;
        }
    }

    private static DateTime sysDate = new DateTime();

    public static String getSytemTimeString() {

        lock (sdftsSysPattern) {
            sysDate =  DateTime.Now;

            return sysDate.ToString(sdftsSysPattern);
        }
    }

    public static void resetToDate(Calendar cal) {
      
    }

    public static void resetToTime(Calendar cal) {
       
    }

    public static long convertMillisToCalendar(Calendar calendar,
            long millis) {

                return millis;

        //lock (tempCalGMT) {
        //    tempCalGMT.setTimeInMillis(millis);
        //    calendar.set(tempCalGMT.get(Calendar.YEAR),
        //                 tempCalGMT.get(Calendar.MONTH),
        //                 tempCalGMT.get(Calendar.DAY_OF_MONTH),
        //                 tempCalGMT.get(Calendar.HOUR_OF_DAY),
        //                 tempCalGMT.get(Calendar.MINUTE),
        //                 tempCalGMT.get(Calendar.SECOND));

        //    return calendar.getTimeInMillis();
        //}
    }

    public static long convertMillisFromCalendar(Calendar calendar,
            long millis) {

                return millis;
        //synchronized (tempCalGMT) {
        //    tempCalGMT.clear();
        //    calendar.setTimeInMillis(millis);
        //    tempCalGMT.set(calendar.get(Calendar.YEAR),
        //                   calendar.get(Calendar.MONTH),
        //                   calendar.get(Calendar.DAY_OF_MONTH),
        //                   calendar.get(Calendar.HOUR_OF_DAY),
        //                   calendar.get(Calendar.MINUTE),
        //                   calendar.get(Calendar.SECOND));

        //    return tempCalGMT.getTimeInMillis();
        //}
    }

    /**
     * Sets the time in the given Calendar using the given milliseconds value; wrapper method to
     * allow use of more efficient JDK1.4 method on JDK1.4 (was protected in earlier versions).
     *
     * @param       cal                             the Calendar
     * @param       millis                  the time value in milliseconds
     */
public static void setTimeInMillis(Calendar cal, long millis) {

//#ifdef JAVA4
        // Use method directly
        throw new NotSupportedException();

//#else
/*
        // Have to go indirect
        synchronized (tempDate) {
            tempDate.setTime(millis);
            cal.setTime(tempDate);
        }
*/

//#endif JAVA4
    }

    /**
     * Gets the time from the given Calendar as a milliseconds value; wrapper method to
     * allow use of more efficient JDK1.4 method on JDK1.4 (was protected in earlier versions).
     *
     * @param       cal                             the Calendar
     * @return      the time value in milliseconds
     */
    public static long getTimeInMillis(Calendar cal) {

//#ifdef JAVA4
        // Use method directly
        throw new NotSupportedException();

//#else
/*
        // Have to go indirect
        return cal.getTime().getTime();
*/

//#endif JAVA4
    }

    public static long convertToNormalisedTime(long t) {
        return convertToNormalisedTime(t, tempCalGMT);
    }

    public static long convertToNormalisedTime(long t, Calendar cal) {

        lock (cal) {
            setTimeInMillis(cal, t);
            resetToDate(cal);

            long t1 = getTimeInMillis(cal);

            return t - t1;
        }
    }

    public static long convertToNormalisedDate(long t, Calendar cal) {

        lock (cal)
        {
            DateTime tmp = new DateTime(t * TimeSpan.TicksPerMillisecond);
            DateTime tmp2 = new DateTime(cal.GetYear(tmp), cal.GetMonth(tmp), cal.GetDayOfMonth(tmp));
            return tmp2.Ticks /TimeSpan.TicksPerMillisecond;
        }
    }

    public static long getNormalisedTime(long t) {

        Calendar cal = tempCalGMT;

        lock (cal)
        {
            DateTime tmp = new DateTime(t * TimeSpan.TicksPerMillisecond);
            DateTime tmp2 = new DateTime(DateTime.MinValue.Year, DateTime.MinValue.Month, DateTime.MinValue.Day,
                cal.GetHour(tmp), cal.GetMinute(tmp), cal.GetSecond(tmp),(int)cal.GetMilliseconds(tmp));
            return tmp2.Ticks / TimeSpan.TicksPerMillisecond;
        }
    }

    public static long getNormalisedTime(Calendar cal, long t) {
        
        lock (cal)
        {
            DateTime tmp = new DateTime(t * TimeSpan.TicksPerMillisecond);
            DateTime tmp2 = new DateTime(DateTime.MinValue.Year, DateTime.MinValue.Month, DateTime.MinValue.Day,
                cal.GetHour(tmp), cal.GetMinute(tmp), cal.GetSecond(tmp), (int)cal.GetMilliseconds(tmp));
            return tmp2.Ticks / TimeSpan.TicksPerMillisecond;
        }
    }

    public static long getNormalisedDate(long d) {
        Calendar cal = tempCalGMT;
        lock (cal)
        {
            DateTime tmp = new DateTime(d * TimeSpan.TicksPerMillisecond);
            DateTime tmp2 = new DateTime(cal.GetYear(tmp), cal.GetMonth(tmp), cal.GetDayOfMonth(tmp));
            return tmp2.Ticks / TimeSpan.TicksPerMillisecond;
        }
    }

    public static long getNormalisedDate(Calendar cal, long d) {

        lock (cal)
        {
            DateTime tmp = new DateTime(d * TimeSpan.TicksPerMillisecond);
            DateTime tmp2 = new DateTime(cal.GetYear(tmp), cal.GetMonth(tmp), cal.GetDayOfMonth(tmp));
            return tmp2.Ticks / TimeSpan.TicksPerMillisecond;
        }
    }

    public static int getZoneSeconds(TimeZoneInfo zone) {
       
        return (int)(zone.BaseUtcOffset.Ticks / TimeSpan.TicksPerMillisecond);
    }

    public static int getZoneMillis(TimeZoneInfo zone, long millis) {

        return (int)( zone.GetUtcOffset(new DateTime(millis * TimeSpan.TicksPerMillisecond)).Ticks /TimeSpan.TicksPerMillisecond);
    }

    /**
     * Returns the indicated part of the given <code>java.util.Date</code> object.
     * @param m the millisecond time value from which to extract the indicated part
     * @param part an integer code corresponding to the desired date part
     * @return the indicated part of the given <code>java.util.Date</code> object
     */
    public static int getDateTimePart(long m, int part) {

        throw new NotSupportedException();
        //synchronized (tempCalGMT) {
        //    tempCalGMT.setTimeInMillis(m);

        //    return tempCalGMT.get(part);
        //}
    }

    //J-

    private static char[][] dateTokens = {
        new char[]{ 'R', 'R', 'R', 'R' }, new char[]{ 'I', 'Y', 'Y', 'Y' }, new char[]{ 'Y', 'Y', 'Y', 'Y' },
        new char[]{ 'I', 'Y' }, new char[]{ 'Y', 'Y' },
        new char[]{ 'B', 'C' }, new char[]{ 'B', '.', 'C', '.' }, new char[]{ 'A', 'D' }, new char[]{ 'A', '.', 'D', '.' },
        new char[]{ 'M', 'O', 'N' }, new char[]{ 'M', 'O', 'N', 'T', 'H' },
        new char[]{ 'D', 'A', 'Y' }, new char[]{ 'D', 'Y' },
        new char[]{ 'I', 'W' }, new char[]{ 'D', 'D' }, new char[]{ 'D', 'D', 'D' },
        new char[]{ 'H', 'H', '2', '4' }, new char[]{ 'H', 'H', '1', '2' }, new char[]{ 'H', 'H' },
        new char[]{ 'M', 'I' },
        new char[]{ 'S', 'S' },
        new char[]{ 'A', 'M' }, new char[]{ 'P', 'M' }, new char[]{ 'A', '.', 'M', '.' }, new char[]{ 'P', '.', 'M', '.' },
        new char[]{ 'F', 'F' }
    };

    private static String[] cSharpDateTokens = {
        "yyyy", "yyyy", "yyyy",
        "yy", "yy",
        "g", "g", "g", "g",
        "MMM", "MMMMM",
        "dddd", "ddd",
        "w", "dd", "D",
        "H", "H", "H",
        "mm", "ss",
        "tt", "tt", "tt", "tt",
        "fff"
    };

    //J+

    /** Indicates end-of-input */
    private const char e =(char) 0xffff;

    /**
     * Converts the given format into a pattern accepted by <code>DateTime.ToString</code>
     *
     * @param format
     */
    public static String toCSharpDatePattern(String format) {

        int          len = format.Length;
        char         ch;
        StringBuilder sb        = new StringBuilder(len);
        Tokenizer    tokenizer = new Tokenizer();

        for (int i = 0; i <= len; i++) {
            ch = (i == len) ? e
                            : format[i];

            if (!tokenizer.next(ch, dateTokens)) {
                int index = tokenizer.getLastMatch();

                if (index >= 0) {
                    sb.Length = (sb.Length - tokenizer.length());
                    sb.Append(cSharpDateTokens[index]);
                }

                tokenizer.reset();

                if (tokenizer.isConsumed()) {
                    continue;
                }
            }

            sb.Append(ch);
        }

        sb.Length = (sb.Length - 1);

        String cSharpPattern = sb.ToString();

        return cSharpPattern;
    }

    /**
     * This class can match 64 tokens at maximum.
     */
    private class Tokenizer {

        private int     last;
        private int     offset;
        private long    state;
        private bool consumed;

        public Tokenizer() {
            reset();
        }

        /**
         * Resets for next reuse.
         *
         */
        public void reset() {

            last   = -1;
            offset = -1;
            state  = 0;
        }

        /**
         * Returns a length of a token to match.
         */
        public int length() {
            return offset;
        }

        /**
         * Returns an index of the last matched token.
         */
        public int getLastMatch() {
            return last;
        }

        /**
         * Indicates whethe the last character has been consumed by the matcher.
         */
        public bool isConsumed() {
            return consumed;
        }

        /**
         * Checks whether the specified bit is not set.
         *
         * @param bit
         */
        private bool isZeroBit(int bit) {
            return (state & (1L << bit)) == 0;
        }

        /**
         * Sets the specified bit.
         * @param bit
         */
        private void setBit(int bit) {
            state |= (1L << bit);
        }

        /**
         * Matches the specified character against tokens.
         *
         * @param ch
         * @param tokens
         */
        public bool next(char ch, char[][] tokens) {

            // Use local variable for performance
            int index = ++offset;
            int len   = offset + 1;
            int left  = 0;

            consumed = false;

            for (int i = tokens.Length; --i >= 0; ) {
                if (isZeroBit(i)) {
                    if (tokens[i][index] == ch) {
                        consumed = true;

                        if (tokens[i].Length == len) {
                            setBit(i);

                            last = i;
                        } else {
                            ++left;
                        }
                    } else {
                        setBit(i);
                    }
                }
            }

            return left > 0;
        }
    }
    }
}
